<script setup lang="ts">
import { ref, type Ref, onMounted, onUnmounted, nextTick, watch } from 'vue'
import { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router'
import PptHeader from './PptHeader.vue'
import { getPptOutlineStream, getPptOutlineStreamFile, httpOutlineToPptContent } from '@/api/ai-ppt'
import { isObjectJson } from '@/utils'
import { BaseResponse } from '@/models/default'
import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
import MarkdownViewer from '@/components/markdown-viewer/index.vue'
import {
  markdownToJson,
  jsonToMarkdown,
  pptOutlineKey,
  pptThemeInputKey,
  pptFileContentKey,
  pptFileIdKey, markdownJsonToServer,
} from '@/views/ai-pptx/ppt-helpers'
import {
  PPTDataJson,
  PPTPage,
  PPTPageList,
  PPTTitle,
  ServerData,
  StreamResponse,
  UploadFileItem,
} from '@/models/ai-ppt'
import { debounce } from '@/utils/tools'
const router = useRouter()
const route = useRoute()

const template_id: Ref<number> = ref(0)

const outlineJson: Ref<PPTDataJson[]> = ref([])

const outline: Ref<string> = ref('')

const isCreateOutline: Ref<boolean> = ref(false)

const saveJsonToStorage = debounce(() => {
  const markdown = jsonToMarkdown(outlineJson.value)
  window.sessionStorage.setItem(pptOutlineKey.value, markdown)
}, 800)

watch(() => outlineJson.value, () => {
  saveJsonToStorage()
}, { deep: true })

const handleCreateOutline = () => {
  isCreateOutline.value = true
  outline.value = ''
  outlineJson.value = []
  const message: string | null = window.sessionStorage.getItem(pptThemeInputKey.value)
  let httpFunction: (...args: string[]) => Promise<StreamResponse>
  let args: string[]
  if (message) {
    httpFunction = getPptOutlineStream
    args = [message]
  } else {
    let file_id: string | null = window.sessionStorage.getItem(pptFileIdKey.value)
    if (file_id) {
      const file_list: UploadFileItem[] = JSON.parse(file_id)
      if (file_list[0]) {
        file_id = file_list[0].file_id
      } else {
        file_id = ''
      }
    }
    const content: string | null = window.sessionStorage.getItem(pptFileContentKey.value)
    args = [file_id, content]
    httpFunction = getPptOutlineStreamFile
  }
  httpFunction(...args).then(({ reader }) => {
    handleReader(reader)
  }).catch(() => {
    isCreateOutline.value = false
  })
}

const next = async () => {
  if (!outlineJson.value.length) {
    return
  }
  if (template_id.value > 0) {
    const lod = ElLoading.service({fullscreen: true, text: 'ppt文件生成中，请稍后...'})
    const serverData: ServerData[] = markdownJsonToServer(outlineJson.value)
    const { id } = await httpOutlineToPptContent(serverData, template_id.value)
    lod.close()
    await router.replace({
      path: '/ai-ppt/generate-ppt',
      query: {
        id: id
      }
    })
  } else {
    await router.replace({
      path: '/ai-ppt/choose-template',
      query: {
        outline: 1,
      }
    })
  }
}

const handleReader = async (reader: ReadableStreamDefaultReader) => {
  let d: boolean, v: Buffer
  try {
    const { value, done } = await reader.read()
    d = done
    v = value
  } catch (e) {
    d = true
  }
  const decoder: TextDecoder = new TextDecoder()
  messageScrollEnd()
  if (!d) {
    let data = decoder.decode(v.buffer)
    outline.value += data
    if (outline.value.trim().startsWith('```markdown')) {
      outline.value = outline.value.replace('```markdown', '')
    }
    await handleReader(reader)
  } else {
    if (v) {
      let data = decoder.decode(v.buffer)
      outline.value += data
    }
    const json: false | BaseResponse = isObjectJson(outline.value)
    if (json !== false && json.code !== 200) {
      outline.value = ''
      ElMessage.error(json.message)
    }
    if (outline.value.endsWith('```')) {
      outline.value.replace('```', '')
    }
    isCreateOutline.value = false
    window.sessionStorage.setItem(pptOutlineKey.value, outline.value)
    showMarkdownJson()
  }
}

const handleBeforeUpload = (e: BeforeUnloadEvent) => {
  if (isCreateOutline.value) {
    const message: string = '大纲正在生成中，您确定要离开吗？'
    e.returnValue = message
    return message
  }
}

onBeforeRouteLeave((to, from, next) => {
  if (isCreateOutline.value) {
    const message: string = '大纲正在生成中，您确定要离开吗？'
    if (window.confirm(message)) {
      next()
    }
  } else {
    next()
  }
})

const messageScrollEnd = () => {
  nextTick(() => {
    const box = document.getElementById('markdown-content')
    const child = document.getElementById('outline-content')
    if (!box || !child) {
      return
    }
    if (child.scrollHeight < box.clientHeight) {
      return
    }
    box.scrollTop = child.scrollHeight - box.clientHeight
  })
}

const showMarkdownJson = () => {
  try {
    outlineJson.value = markdownToJson(outline.value)
  } catch (e) {
    ElMessage.error('大纲解析失败，重新生成')
    handleCreateOutline()
  }
}

onMounted(() => {
  const temp_id: string = route.query.tid + ''
  if (temp_id && !isNaN(parseInt(temp_id))) {
    template_id.value = parseInt(temp_id)
  }
  window.addEventListener('beforeunload', handleBeforeUpload)
  const storeMkOutline: string = window.sessionStorage.getItem(pptOutlineKey.value)
  if (storeMkOutline) {
    outline.value = storeMkOutline
    showMarkdownJson()
    return
  }
  handleCreateOutline()
})

onUnmounted(() => {
  window.removeEventListener('beforeunload', handleBeforeUpload)
})

const deleteJsonItem = async (
  parent: PPTDataJson[] | PPTPage[] | PPTPageList[],
  index: number,
) => {
  const confirm = await ElMessageBox.confirm('确认删除内容？', '确认提示').then(() => true).catch(() => false)
  if (confirm) {
    parent.splice(index, 1)
  }
}
/**
 * 在上方新增同级
 */
const addBefore = (
  parent: PPTDataJson[] | PPTPage[] | PPTPageList[],
  item: PPTTitle | PPTPage | PPTPageList,
  index: number,
) => {
  if (item.type === 'title') {
    const addItem: PPTTitle = {
      type: 'title',
      title_title: '',
      title_sub_title: '',
      pages: [],
    }
    parent.splice(index, 0, addItem)
  } else if (item.type === 'page') {
    const addItem: PPTPage = {
      type: 'page',
      page_title: '',
      list: [],
    }
    parent.splice(index, 0, addItem)
  } else if (item.type === 'page_sub_title') {
    const addItem: PPTPageList = {
      type: 'page_sub_title',
      page_sub_title: '',
      page_content: '',
    }
    parent.splice(index, 0, addItem)
  }
}

/**
 * 新增下级
 */
const addNext = (item: PPTTitle | PPTPage) => {
  if (item.type === 'title') {
    item.pages.unshift({
      type: 'page',
      page_title: '',
      list: [],
    })
  } else {
    item.list.unshift({
      type: 'page_sub_title',
      page_sub_title: '',
      page_content: '',
    })
  }
}
</script>

<template>
  <ppt-header title="智能生成PPT" />
  <div class="input-section" style="margin-top: 30px">
    <div class="markdown-content" id="markdown-content" v-if="outlineJson.length === 0">
      <markdown-viewer :value="outline" id="outline-content" />
    </div>
    <div class="markdown-json-content" v-else>
      <div class="font-bold font-size-3xl flex justify-content-center line-height-2xl">{{ outlineJson[0]['title'] }}
      </div>
      <div class="markdown-content" style="padding-left: 80px">
        <el-timeline>
          <template v-for="(item, index) in outlineJson">
            <el-timeline-item
              center
              v-if="item.type === 'title'"
              hide-timestamp
              placement="top"
              size="large"
              type="primary"
              hollow
            >
              <template #default>
                <div class="font-bold font-size-xl padding-left-10 line-time">第 {{ index }} 章</div>
                <el-input v-model="item.title_title" class="title-input" />
                <div class="icons">
                  <el-tooltip content="在上方新增同级" effect="dark" placement="top">
                    <el-icon @click.stop="addBefore(outlineJson, item, index)">
                      <component is="FirstAidKit" />
                    </el-icon>
                  </el-tooltip>
                  <el-tooltip content="在下方新增同级" effect="dark" placement="top">
                    <el-icon
                      @click.stop="addBefore(outlineJson, item, index + 1)"
                      style="transform: rotate(180deg)"
                    >
                      <component is="FirstAidKit" />
                    </el-icon>
                  </el-tooltip>

                  <el-tooltip content="在下方新增下属" effect="dark" placement="top">
                    <el-icon @click="addNext(item)">
                      <component is="CirclePlus" />
                    </el-icon>
                  </el-tooltip>
                  <el-tooltip content="删除" effect="dark" placement="top">
                    <el-icon @click.stop="deleteJsonItem(outlineJson, index)">
                      <component is="CircleClose" />
                    </el-icon>
                  </el-tooltip>
                </div>
              </template>
            </el-timeline-item>
            <template v-if="item.type === 'title'" v-for="(page, i) in item.pages">
              <el-timeline-item
                center
                v-if="index > 0"
                hide-timestamp
                placement="top"
                size="normal"
              >
                <template #default>
                  <div class="font-bold font-size-l padding-left-10 line-time">{{ index }}.{{ i + 1 }}</div>
                  <el-input v-model="page.page_title" class="title-input page_title" />
                  <div class="icons">
                    <el-tooltip content="在上方新增同级" effect="dark" placement="top">
                      <el-icon @click.stop="addBefore(item.pages, page, i)">
                        <component is="FirstAidKit" />
                      </el-icon>
                    </el-tooltip>
                    <el-tooltip content="在下方新增同级" effect="dark" placement="top">
                      <el-icon
                        @click.stop="addBefore(item.pages, page, i + 1)"
                        style="transform: rotate(180deg)"
                      >
                        <component is="FirstAidKit" />
                      </el-icon>
                    </el-tooltip>
                    <el-tooltip content="在下方新增下属" effect="dark" placement="top">
                      <el-icon @click="addNext(page)">
                        <component is="CirclePlus" />
                      </el-icon>
                    </el-tooltip>
                    <el-tooltip content="删除" effect="dark" placement="top">
                      <el-icon @click.stop="deleteJsonItem(item.pages, i)">
                        <component is="CircleClose" />
                      </el-icon>
                    </el-tooltip>
                  </div>
                </template>
              </el-timeline-item>
              <el-timeline-item
                center
                v-if="page.list.length"
                v-for="(pl, k) in page.list"
                hide-timestamp
              >
                <template #default>
                  <div class="padding-left-10 line-time">{{ index }}.{{ i + 1 }}.{{ k + 1 }}</div>
                  <el-input v-model="pl.page_sub_title" class="title-input page_sub_title" />
                  <div class="icons">
                    <el-tooltip content="在上方新增同级" effect="dark" placement="top">
                      <el-icon @click.stop="addBefore(page.list, pl, k)">
                        <component is="FirstAidKit" />
                      </el-icon>
                    </el-tooltip>
                    <el-tooltip content="在下方新增同级" effect="dark" placement="top">
                      <el-icon
                        @click.stop="addBefore(page.list, pl, k + 1)"
                        style="transform: rotate(180deg)"
                      >
                        <component is="FirstAidKit" />
                      </el-icon>
                    </el-tooltip>
                    <el-tooltip content="删除" effect="dark" placement="top">
                      <el-icon @click.stop="deleteJsonItem(page.list, k)">
                        <component is="CircleClose" />
                      </el-icon>
                    </el-tooltip>
                  </div>
                </template>
                <template #dot></template>
              </el-timeline-item>
            </template>
          </template>
        </el-timeline>
      </div>
    </div>
    <div class="markdown-footer" v-if="outline && !isCreateOutline">
      <el-button type="warning" icon="back" @click="() => { router.back() }">返回</el-button>
      <div>
        <el-button type="primary" icon="Refresh" @click="handleCreateOutline()">重新生成</el-button>
        <el-button type="success" @click="next" :disabled="!outlineJson.length">
          下一步&nbsp;
          <el-icon>
            <component is="Right" />
          </el-icon>
        </el-button>
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
.input-section {
  width: calc(100% - 80px);
  max-width: 1000px;
  box-shadow: var(--el-box-shadow);
  margin: 100px auto 0;
  background: rgba(255, 255, 255, 0.6);
  border-radius: 8px;
  padding: 15px;

  @include scrollBar;

  .input-content {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    border: var(--el-border);
    background: #FFF;
    padding: 10px 10px 10px 0;
    border-radius: 6px;
    border-width: 2px;

    &:deep(.el-textarea__inner) {
      box-shadow: none;
    }

    .btn {
      width: 30px;
      height: 30px;
      display: flex;
      justify-content: center;
      align-items: center;
      cursor: pointer;
      border-radius: 4px;
      color: var(--el-text-color-secondary);
      font-size: 40px;

      &.disable {
        cursor: no-drop;
        opacity: 0.6;
      }
    }
  }

  .markdown-content {
    max-height: calc(100vh - 280px);
    overflow: auto;
    min-width: 300px;
  }

  .markdown-footer {
    display: flex;
    padding: 20px 0 10px;
    justify-content: space-between;
    align-items: center;
  }

  .title-input {
    width: 700px;

    &.page_title {
      :deep(.el-input__wrapper .el-input__inner) {
        font-size: 20px;
        color: #333333;
        font-weight: bold;
        height: 30px;
      }
    }

    &.page_sub_title {
      :deep(.el-input__wrapper .el-input__inner) {
        font-size: 14px;
        color: #666666;
        height: 26px;
      }
    }

    :deep(.el-input__wrapper) {
      box-shadow: none;
      background: transparent;

      .el-input__inner {
        font-size: 26px;
        color: #101010;
        font-weight: bold;
        height: 40px;
      }

      &:hover {
        box-shadow: 0 0 0 1px var(--el-border-color) inset;
        background: #FFF;
      }

      &:has(.el-input__inner:focus) {
        box-shadow: 0 0 0 1px var(--el-color-primary) inset;
        background: #FFF;
      }
    }
  }

  .line-time {
    position: absolute;
    left: -130px;
    width: 80px;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: flex-end;
  }

  .el-timeline-item {
    padding-bottom: 10px;

    :deep(.el-timeline-item__content) {
      display: flex;
      justify-content: space-between;
      gap: 20px;
      align-items: center;
      position: relative;
      top: 3px;
    }

    .icons {
      height: 100%;
      display: flex;
      justify-content: space-between;
      align-items: center;
      opacity: 0;
      color: #676767;
      width: 130px;
      padding-right: 20px;

      .el-icon {
        cursor: pointer;
      }
    }

    &:hover {
      .icons {
        opacity: 1;
      }
    }
  }
}
</style>